perm filename EPROP8.TXT[COM,LSP] blob
sn#822839 filedate 1986-08-18 generic text, type T, neo UTF8
-*- Mode: LISP; Syntax: Common-Lisp -*-
Error Proposal #8 by KMP 8/4/86, Page 1
Error System Proposal #8 by Kent M. Pitman (KMP@SCRC-STONY-BROOK.ARPA).
This draft incorporates numerous suggestions, criticisms, and questions
made by:
Paul ... ;Greek@Hudson.DEC.COM
Alan Bawden ;ALAN@MC.LCS.MIT.EDU
Andy Daniels ;Daniels.PA@Xerox.COM
Susan Lander ;LANDER%umass-cs.csnet@CSNET-RELAY.ARPA
Richard Mlynarik ;MLY@MC.LCS.MIT.EDU
David A. Moon ;Moon@SCRC-STONY-BROOK.ARPA
Not all of their suggestions made it into this proposal however, either
because of some technical disagreement or limitations of time, so it
shouldn't be seen as contradictory if they criticize aspects of the proposal.
This draft was prepared for limited distribution at the Lisp Conference.
It is a somewhat major overhaul of the previous proposal (#5). Much
constraint propagation of the changes had to be done throughout the
examples. If you note errors that seem to be due to the old proposal
showing through (or errors of any other sort), please report them.
The major features of this proposal include:
* Elimination of any association between proceed cases and conditions.
There is no longer any type descrimination based on condition type.
Proceed functions no longer take condition arguments. The motivation
for this change is primarily that proceed cases are not generally
capable of continuing from more than one condition and the relevant
state is usually available lexically. In the few cases where condition
information is needed (eg, PROCEED and ABORT), the information should
be a normal piece of data transfered between the handler and proceed
case, rather than a special piece of information which is always
required but seldom used.
* The presentation has been changed to add additional motivation.
Several pages of introductory exposition and examples have been added.
* Some minor wording changes have been made to clarify the definitions
of functions mentioned to be confusingly described by some or all of
Landers, Daniels, and KMP during their work on sample implementations.
* Report functions for conditions can access slot names through
variables if the :REPORT option is used in DEFINE-CONDITION.
[I'm prepared for some discussion on this, in particular relating
to the choice of name/package needed for the variables which
relate to pre-defined conditions.]
* I added a :HANDLER-FUNCTION in DEFINE-CONDITION for people that
don't like :HANDLE. There seem to be camps on both sides. Although,
some people seem to like only one and be actively offended by the
other, my intuition is that the best compromise is to keep both for
now. They each have their advantages and it's easy enough to ignore
the other if you're a fan of only one.
* FIND-PROCEED-CASE has been made undefined for a proceed-case name
of NIL.
Error Proposal #8 by KMP 8/4/86, Page 2
* The argument to BREAK is now optional and defaults to a condition
of type SIMPLE-CONDITION rather than SIMPLE-BREAK. DEBUG now takes
arguments just like BREAK and its only real difference is that it
sets up the a proceed-case of type PROCEED to allow the user to
return from the debugger.
* The relation between nested single-clause CONDITION-CASE forms and
a single CONDITION-CASE form with many clauses is now better defined.
* Both INVOKE-PROCEED-CASE and named proceed functions will do argument
defaulting.
* Supplied-p variables are allowed in a restricted way in
DEFINE-PROCEED-FUNCTION.
* Introduction of some new condition types.
* Explicit mention of pre-defined proceed functions PROCEED, ABORT,
USE-VALUE, and STORE-VALUE. (I expect that these latter two may be
subject of debate.)
* A portable implementation was made of Proposal #6 which was not
distributed. It was fairly close to Proposal #5, but had to ignore
certain restrictions of Proposal #5. Except for some minor syntactic
issues, Proposal #6 was more or a superset of Proposal #8, so it
should be trivial to convert it to match this proposal.
With slight modifications, it should be possible to make
it match this proposal.
Error Proposal #8 by KMP 8/4/86, Page 3
The following issues are unresolved. At some later date, I will probably
distribute a ballot containing specific proposals for what to do with
some or all of these issues:
* The wording is sloppy about whether magic forms in this proposal are
described as Special Forms or Macros. In principle, they could be defined
either way. My intuition is to call them special forms, especially since
I don't want people to be able to rely on their macroexpansions in any
useful way, and since the macroexpansions may vary greatly from system
to system.
* It is intended that both a named proceed function and INVOKE-PROCEED-CASE
will be able to do optional argument resolution based on the information
supplied in DEFINE-PROCEED-FUNCTION. I believe the wording is incompletely
changed in a few places. Please report cases as you notice them.
* Something might still want to be done about the fact that there is both
a type and a special form named PROCEED-CASE.
* Greek has suggested we should change the syntax of a PROCEED-CASE
clause from
(name bvl [keyword arg]* . body)
to
((name [keyword arg]*) bvl . body)
Presumably in the case of no keyword and arg, just the name could still
be allowed. I'm still thinking about this. Comments from others are invited.
* The set of condition types could probably still use some work. There
may still be issues of whether the conditions belong in their own package.
* Some business with handling of SERIOUS-CONDITION, etc. needs to be
resolved. Daniels suggests that we should have SIGNAL test for
SERIOUS-CONDITION and not rely on the default handlers to implement
the semantics of SERIOUS-CONDITION. I'm susceptible to this, but also
have several other conflicting ideas on how this should go. More input
is needed here. One possibility is to flush default handlers entirely.
My intuition is that they would be useful, but the Lispm does without
them right now and it may be that they're premature at this point.
Error Proposal #8 by KMP 8/4/86, Page 4
INTRODUCTION
------------
Often we find it useful to describe a function in terms of its behavior
in ``normal situations.'' For example, we may say informally that the
function + returns the sum of its arguments or that the function READ-CHAR
returns the next available character on a given input stream.
Sometimes, however, an ``exceptional situation'' will arise which does not fit
neatly into such descriptions. For example, + might receive an argument which
is not a number, or READ-CHAR might receive a single argument which was a
stream that had no more available characters. This distinction between normal
and exceptional situations is in some sense arbitrary, but is often very
useful in practice.
Exceptional situations are not always detected. If the failure to recognize an
exceptional situation would result in an undesirable result or behavior, we
call that situation an ``error.'' Some errors are detected and some are not.
It is important to note, however, that not all exceptional situtations are
errors. For example, a program which is typing out a long line of text may
notice that it is at the end of the line. It is possible that no real harm
will result from continuing to type past the end of the line because the
operating system will simply force a carriage return on the output device and
continue typing on the next line. However, it may still be interesting to
establish a protocol whereby that program can inform its callers of
end-of-line exceptions. The controlling program could then opt to deal with
these situations in interesting ways at certain times. For example, it might
choose to terminate printing, obtaining a end-of-line truncation. The
important thing, however, is that the failure of the controlling program to
provide advice about the situation need not prevent the printer program from
operating correctly.
Mechanisms for dealing with exceptional situations vary widely. When an
exceptional situation is encountered, a program may attempt to handle the
situation by returning a distinguished value, returning an additional value,
setting a variable, calling a function, performing a special tranfer of
control, or by stopping the program altogether and entering the debugger.
For the most part, the primitives described in this chapter do not introduce
any fundamentally new way of dealing with exceptional situations. Rather, they
encapsulate and formalize useful patterns of data and control flow which have
been seen to be useful in dealing with exceptional situations.
A proper conceptual approach to errors would rightly begin from first
principles with a discussion of ``conditions'' in general and later work its
way up to the concept of an ``error,'' just one of the many kinds of
conditions. However, given the widespread primitive state of error handling
technology, a proper buildup may be as inappropriate as requiring that a
beggar learn to cook a gourmet meal before being allowed to eat. As such,
we'll first deal with the essentials -- error handling -- and then later we'll
go back and fill in the missing details.
Error Proposal #8 by KMP 8/4/86, Page 5
OVERVIEW
--------
If an action would lead to an error situation, we say that the action ``is an
error.'' However, we also provide a formal mechanism whereby a program may
identify the presence of an error (or any unusual situation). We call this
mechanism ``signaling.'' A program that identifies an error using this
mechanism is said to ``signal an error.''
The simplest way to signal an error is to use the ERROR function with
FORMAT-style arguments describing the error for the sake of the user
interface. If ERROR is called and there are no intervening handlers
(described later), the debugger will be entered and the error message
will be typed out. For example, you might see an interaction such as:
Lisp> (DEFUN FACTORIAL (X)
(COND ((OR (NOT (TYPEP X 'INTEGER)) (MINUSP X))
(ERROR "}S is not a valid argument to FACTORIAL." X))
((ZEROP X) 1)
(T (* X (FACTORIAL (- X 1))))))
=> FACTORIAL
Lisp> (FACTORIAL 20)
=> 2432902008176640000
Lisp> (FACTORIAL -1)
Error: -1 is not a valid argument to FACTORIAL.
To proceed, type CONTINUE followed by an option number:
0: Return to Lisp Toplevel
Debug>
In some cases, you may have an idea of a reasonable recovery strategy for
this particular error. In that case, you can use the function CERROR, which
specifies information about what would happen if you were to simply continue
from the call to CERROR. For example, you might write:
Lisp> (DEFUN FACTORIAL (X)
(COND ((NOT (TYPEP X 'INTEGER))
(ERROR "}S is not a valid argument to FACTORIAL." X))
((MINUSP X)
(LET ((X-MAGNITUDE (- X)))
(CERROR "Compute -(}D!) instead."
"(-}D)! is not defined." X-MAGNITUDE)
(- (FACTORIAL X-MAGNITUDE))))
((ZEROP X) 1)
(T (* X (FACTORIAL (- X 1))))))
=> FACTORIAL
Lisp> (FACTORIAL -3)
Error: (-3)! is not defined.
To proceed, type CONTINUE followed by an option number:
0: Compute -(3!) instead.
1: Return to Lisp Toplevel
Debug> CONTINUE 0
=> -6
Error Proposal #8 by KMP 8/4/86, Page 6
By default, a call to ERROR will force entry into the debugger. You can
override that behavior in a variety of ways. The simplest (and most blunt)
tool for inhibiting entry to the debugger is to use IGNORE-ERRORS. In the
normal situation, forms in the body of IGNORE-ERRORS are evaluated
sequentially and the last value is returned. If an error is signaled,
IGNORE-ERRORS immediately returns NIL; the debugger is not entered and not
error message is printed. For example:
Lisp> (SETQ A 3)
=> 3
Lisp> (IGNORE-ERRORS (+ A 'A))
=> NIL, #<ARITHMETIC-ERROR 3437523>
The second return value is an object which represents the kind of error.
This will be explained in greater detail later.
In many cases, however, IGNORE-ERRORS is not desirable because it catches
too many kinds of errors. Contrary to popular belief, a program which
doesn't enter the debugger is not necessarily better than one which does.
Excessive use of IGNORE-ERRORS may keep you out of the debugger, but it
may not increase your program's reliability. In general, it is better to
attempt to trap only the particular kinds of errors that you believe could
legitimately happen. That way, if an unexpected error comes along, you
will still find out about it.
IGNORE-ERRORS is a useful special case built from a more general facility,
called CONDITION-CASE, which allows you to "catch" only certain kinds of
conditions (including non-error conditions) without catching others.
For example, an effect equivalent to that of IGNORE-ERRORS above is
achieved in the following example:
Lisp> (SETQ A 3)
=> 3
Lisp> (CONDITION-CASE (+ A 'A)
(ERROR (CONDITION) (VALUES NIL CONDITION)))
=> NIL, #<ARITHMETIC-ERROR 3437523>
However, using CONDITION-CASE it is possible to specify a more specific
condition type than just error. Condition types are explained in detail
later, but the syntax will looks roughly like the following:
Lisp> (MAKUNBOUND 'A)
=> 3
Lisp> (CONDITION-CASE (+ A 'A)
(ARITHMETIC-ERROR (CONDITION) (VALUES NIL CONDITION)))
Error: The variable A is unbound
To proceed, type CONTINUE followed by an option number:
0: Retry getting the value of A.
1: Specify a value of A to use this time.
2: Specify a value of A to store and use.
3: Return to Lisp Toplevel
Debug>
Error Proposal #8 by KMP 8/4/86, Page 7
In fact, blind transfer of control to a CONDITION-CASE is only one
possible kind of recovery action that can be taken when a condition is
signaled. The low-level mechanism offers a great deal of flexibility in
how to proceed once a condition has been signaled.
The basic idea behind condition handling is that a piece of code
called the ``signaler'' recognizes and announces the existence of an
exceptional situation using the SIGNAL primitive or some primitive
built on SIGNAL (such as ERROR).
The process of signaling involves the search for and invocation of
a ``handler,'' a piece of code which will attempt to deal appropriately
with the situation.
If a handler is found, it may either ``handle'' the situation by
performing some non-local transfer of control or ``decline,'' by
failing to perform a non-local transfer of control. If it declines,
other handlers are sought.
Since the lexical environment of the signaler will not be available
to handlers, a data structure called a ``condition'' is created to
represent explicitly the relevant state of the situation. A condition
is either created explicitly using MAKE-CONDITION and then passed to
a function such as SIGNAL or is created implicitly by a function
such as SIGNAL when given appropriate non-condition arguments.
In order to handle the error, a handler is permitted to use any
non-local transfer of control such as GO to a tag in a TAGBODY,
RETURN from a BLOCK, or THROW to a CATCH. In addition, however,
structured abstractions on these primitives are provided later for
convenience in exception handling.
A handler can be made dynamically accessible to a program by use of
CONDITION-BIND. For example, to create a handler for a condition
of type ARITHMETIC-ERROR, one might write:
(CONDITION-BIND ((ARITHMETIC-ERROR ...handler...))
...body...)
The handler is a function of one argument, the condition. If a condition
of the designated type is signaled while body is executing (and there are
no intervening) handlers, the handler would be invoked on the given
condition, allowing it the option of transfering control. So, for example,
to write a macro which executed a body returning either its value(s) or
the two values NIL and the condition, one might write:
(DEFMACRO WITHOUT-ARITHMETIC-ERRORS (&BODY FORMS)
(LET ((TAG (GENSYM)))
`(BLOCK ,TAG
(CONDITION-BIND ((ARITHMETIC-ERROR #'(LAMBDA (CONDITION)
(RETURN-FROM ,TAG (VALUES NIL CONDITION)))))
,@BODY))))
Error Proposal #8 by KMP 8/4/86, Page 8
Of course, the ability of the handler to usefully handle an exceptional
situation is related to the quality of the information it is provided.
For example, if all errors were signaled by typing
(ERROR "...some format string...")
then the only piece of information that would be accessible to the
handler would be an object of type SIMPLE-ERROR which had a slot
containing the format string.
If this were done, STRING-EQUAL would be the preferred way to tell one
error from another, and it would be very hard to allow flexibility in
the presentation of error messages because existing handlers would tend
to be broken even tiny variations in the wording of an error message.
This phenomenon has been the major failing of most error systems
previously available in Lisp. It is fundamentally important to de-couple
the error message string (the human interface) from the objects which
formally represent the error state (the program interface).
We therefore have the notion of typed conditions, and of formal
operations on conditions of each type which make those conditions
inspectable in a structred way.
Some condition types are defined by this proposal, but the set of
condition types is extensible using the DEFINE-CONDITION special form.
Here's an example of a possible definition of the / function:
(DEFUN / (NUMERATOR DENOMINATOR)
(COND ((OR (NOT (NUMBERP NUMERATOR)) (NOT (NUMBERP DENOMINATOR)))
(ERROR "(/ '}S '}S) - Bad arguments." NUMERATOR DENOMINATOR))
((OR (EQL DENOMINATOR 0) (EQL DENOMINATOR 0.0))
(ERROR 'DIVISION-BY-ZERO
:OPERATOR '/
:OPERANDS (LIST NUMERATOR DENOMINATOR)))
(T ...)))
Note that in the first case we have used ERROR with a string. This means
that the signaled error will be of type SIMPLE-ERROR and only handlers
for CONDITION, ERROR, and SIMPLE-ERROR will attempt to handle it.
In the second case, however, we use a more specific error type. This means
that handlers which have been established specifically for DIVISION-BY-ZERO
or ARITHMETIC-ERROR will also have a shot at correcting the error.
In Maclisp, typing
(ERROR "Unbound variable" VAR 'UNBND-VRBL)
indicated both that the error was an unbound variable error AND that the
handler was permitted to return information about how to proceed. Sometimes,
handlers would return information that the signaler was not prepared for
and confusion would result. More often, people would simply not write
error handlers for fear that the erring programs would not be prepared
to have the error really be handled.
This condition system, which descends from the Lisp Machine error system,
corrects this deficiency by creating a logical separation between the act
of signaling an error of a particular type and the act of saying that a
particular way of proceeding is appropriate. In the example above,
simply signaling an error does not imply a willingness on the part of the
signaller to cooperate in any corrective action. Consider:
Lisp> (+ (/ 3 0) 7)
Error: Attempt to divide 3 by 0.
To proceed, type CONTINUE followed by an option number:
0: Return to Lisp Toplevel
Debug> CONTINUE 0
Returned to Lisp Toplevel
Lisp>
Error Proposal #8 by KMP 8/4/86, Page 9
We introduce the notion of ``proceed cases'' and ``proceed functions.''
There will be very obvious differences, but it may help to model a
``proceed case'' as somewhat analagous to CATCH and a ``proceed function''
as somewhat analagous to a THROW. In particular, a use of the PROCEED-CASE
special form sets up a dynamically accessible contour during which control
can be returned to a clause in the PROCEED-CASE by calling a proceed
function.
The signaler recognizes a condition and announces its existence using
SIGNAL or some primitive built on SIGNAL (such as ERROR). The signaler
(or, in fact, any piece of code) may specify zero or more ways, called
proceed cases, in which the computation can be exited. For example,
we could rewrite the / example from the previous page as:
(DEFUN / (NUMERATOR DENOMINATOR)
(LOOP
(PROCEED-CASE (RETURN
(COND ((OR (NOT (NUMBERP NUMERATOR))
(NOT (NUMBERP DENOMINATOR)))
(ERROR "(/ '}S '}S) - Bad arguments."
NUMERATOR DENOMINATOR))
((OR (EQL DENOMINATOR 0) (EQL DENOMINATOR 0.0))
(ERROR 'DIVISION-BY-ZERO
:OPERATOR '/
:OPERANDS (LIST NUMERATOR DENOMINATOR)))
(T ...)))
(NIL (&OPTIONAL (ARG1 (READ-TYPED-OBJECT 'NUMBER "Numerator: "))
(ARG2 (READ-TYPED-OBJECT 'NUMBER "Denominator: ")))
:REPORT "Provide alternate arguments for use by the / function."
(SETQ NUMERATOR ARG1 DENOMINATOR ARG2))
(NIL (&OPTIONAL (RESULT (READ-TYPED-OBJECT 'NUMBER "Result: ")))
:REPORT "Provide a value to return from the / function."
(RETURN RESULT)))))
In this case, there are two "anonymous" proceed cases (which will generally
only be used from within the debugger) that allow control to continue in
either of two ways:
Lisp> (+ (/ 3 0) 7)
Error: Attempt to divide 3 by 0.
To proceed, type CONTINUE followed by an option number:
0: Provide alternate arguments for use by the / function.
1: Provide a value to return from the / function.
2: Return to Lisp Toplevel
Debug> CONTINUE 0
Numerator: 4
Denominator: 2
=> 9
Error Proposal #8 by KMP 8/4/86, Page 10
In addition to anonymous proceed cases, we can also have named proceed cases.
The interesting point here is that you can write:
(PROCEED-CASE (INVOKE-PROCEED-CASE 'FOO 3)
(FOO (X) (+ X 1)))
to add 3 to 1, returning 4. This trivial case is conceptually analagous to
writing:
(+ (CATCH 'SOMETHING (THROW 'SOMETHING 3)) 1)
However, the key features that PROCEED-CASE offers which CATCH does not is
that you can use FIND-PROCEED-CASE to determine whether there is an
appropriate PROCEED-CASE awaiting before trying to transfer to it, and you
can use COMPUTE-PROCEED-CASES to compute a list of information about all
available proceed cases, including those named proceed cases which are
shadowed by a nearer proceed case having the same name. It is this
ability to inspect the available options which makes this condition system
capable of elaborate corrective procedures.
For example, the code for Lisp's SYMBOL-FUNCTION primitive might signal an
unbound variable error by doing:
... (PROCEED-CASE (ERROR "The variable }S is unbound." VARIABLE)
(PROCEED (CONDITION)
:REPORT (FORMAT T "Retry getting the value of }S." VARIABLE)
(DECLARE (IGNORE CONDITION))
(SYMBOL-VALUE VARIABLE))
(USE-VALUE (VALUE)
:REPORT (FORMAT T "Specify a value of }S to use this time." VARIABLE)
VALUE)
(STORE-VALUE (VALUE)
:REPORT (FORMAT T "Specify a value of }S to store and use." VARIABLE)
(SETF (SYMBOL-VALUE VARIABLE) VALUE)
VALUE)) ...
An error handler can then be written to handle unbound-variable errors by
making the variable evaluate to itself by writing:
(CONDITION-BIND ((UNBOUND-VARIABLE
#'(LAMBDA (CONDITION)
(IF (FIND-PROCEED-CASE 'USE-VALUE)
(INVOKE-PROCEED-CASE
'USE-VALUE
(CELL-ERROR-NAME CONDITION))))))
...body...)
Also, by using DEFINE-PROCEED-FUNCTION, you can define a ``proceed function''
that encapsulates the following idiom:
(IF (FIND-PROCEED-CASE case-name)
(INVOKE-PROCEED-CASE case-name . data))
The proceed functions PROCEED, USE-VALUE, STORE-VALUE, and ABORT are pre-defined
by this proposal of this proposal, but could have been defined using
DEFINE-PROCEED-FUNCTION. Using the proceed function, the above CONDITION-BIND
could more concisely be written:
(CONDITION-BIND ((UNBOUND-VARIABLE
#'(LAMBDA (CONDITION)
(USE-VALUE (CELL-ERROR-NAME CONDITION)))))
...body...)
The presence of these proceed cases and proceed functions allows a condition
handler much flexibility in its decision about how to correct an error under
program control. For example, the functions PROCEED, USE-VALUE, or STORE-VALUE
can be called from a handler and will transfer control to the corresponding
clauses in the innermost active proceed case. If no handler performs this
transfer of control automatically, the debugger is entered and the user is
allowed to perform this transfer of control manually.
Error Proposal #8 by KMP 8/4/86, Page 11
It is not necessary that all conditions be handled. Some conditions are
trivial enough that a failure to handle them may be disregarded. Others,
which we will call ``serious conditions'' must be handled in order to
assure correct program behavior. If a serious condition is signaled but
no handler is found, the debugger will be entered so that the user may
interactively specify how to proceed.
Serious conditions which result from incorrect programs or data are
called ``errors.'' Not all serious conditions are errors, however. Storage
conditions are examples of serious conditions that are not errors.
For example, the control stack may legitimately overflow without a program
being in error. Even though a stack overflow is not necessarily a program
error, it is serious enough to warrant entry to the debugger if the
condition goes unhandled.
Some types of conditions are predefined by the system. All types of
conditions are subtypes of CONDITION. That is, (TYPEP C 'CONDITION) is
true iff C is a condition.
Systems with non-hierarchical type systems are allowed to use such
non-hierarchical types in a CL-visible way as long as (TYPEP c 'CONDITION)
is true for such conditions, and as long as all of the subtype relationships
specified in this document are preserved. No mechanism is currently provided
at this time for creating condition types that directly exploit any kind of
non-hierarchical inheritance, but portable programs should expect that some
implementations will use multiple inheritance. In particular, portable
programs should not assume that the subtype relationships indicated in this
document are mutually exclusive.
For example, it follows from the subtype descriptions contained in this
document that in all implementations:
(TYPEP c 'CONTROL-ERROR) implies (TYPEP c 'ERROR),
but you should be careful to note that:
(TYPEP c 'CONTROL-ERROR) does NOT imply (NOT (TYPEP c 'CELL-ERROR)).
Error Proposal #8 by KMP 8/4/86, Page 12
Signaling Conditions
When a condition is signaled, the system tries locate the most appropriate
handler for the condition and invoke that handler. Handlers are located
according to the following rules:
* Check for locally (dynamically) bound handlers. These handlers are
established by CONDITION-BIND or abstractions built on CONDITION-BIND.
* If no appropriate bound handler is found, check for the default handler
first of the signaled type and then of each of its superiors. These
handlers can be established as part of a DEFINE-CONDITION expression.
Conceptually, the default handlers behave as if they were handlers bound at
the outermost level such that the most specific default handlers are tried
first.
If an appropriate handler is found, it is called. In some circumstances,
the handler may ``decline'' by simply returning without performing a
non-local transfer of control. In such cases, the search for an
appropriate handler is picked up where it left off, as if the called
handler had never been present.
If no bound handler or default handler is found, or if all handlers which were
found decline, SIGNAL returns the condition which was signaled.
Handling Conditions
A ``handler'' is a function of one argument, the condition to be handled. The
handler may inspect the object (using primitives described in another section)
to be sure it is `interested' in handling the condition. After inspecting the
condition, the handler must take one of the following actions:
* It may decline to handle the condition, by simply returning. When this
happened, the returned values are ignored and the effect is the same as
if the handler had been invisible to the mechanism seeking to find a
handler. The next handler in line will be tried, or if no such handler,
the default action for the given condition will be taken. A default handler
may also decline, in which case the condition will go unhandled.
* It may perform some non-local transfer of control using GO, RETURN, THROW,
ABORT, or INVOKE-PROCEED-CASE.
* It may signal another condition.
* It may invoke the interactive debugger.
In some cases, it may be useful to ``report'' a condition or a proceed case
to a user or a log file of some sort. When the printer is invoked on a condition
or proceed case while *PRINT-ESCAPE* is NIL, the report-function for that object
is invoked. In particular, this means that an expression like
(FORMAT T "}A" CONDITION)
will invoke CONDITION's report function. Because of this, no special function
is provided for invoking the report function of a condition or a proceed case.
When *PRINT-ESCAPE* is not NIL, then the object can print in some useful
fashion according to the style of the implementation. It is not expected that
a condition will be able to print re-readably, so something like
#<ARITHMETIC-ERROR.17>
is fine.
Although it follows from the description above, it is perhaps worth noting
explicitly that the lookup procedure described here will prefer a general
but "more local" handler over a specific but "less local" handler. Experience
with existing condition systems suggests that this is a reasonable approach
and works adequately in most situations. Some care should be taken when binding
handlers for very general kinds of conditions, such as is done in IGNORE-ERRORS.
Often, binding for a more specific condition type than ERROR can be more
appropriate.
Error Proposal #8 by KMP 8/4/86, Page 13
PROGRAM INTERFACE TO THE CONDITION SYSTEM
-----------------------------------------
ERROR datum &rest arguments [Function]
Invokes the signal facility on a condition. If the condition is not handled,
(DEBUG condition) is done.
If DATUM is a condition, then that condition is used directly.
In this case, it is an error for ARGUMENTS to be non-NIL.
If DATUM a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-ERROR
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
CERROR proceed-format-string datum &rest arguments [Function]
Invokes the error facility on a condition. If the condition is not handled,
(DEBUG condition). While signaling is going on, and while in the
debugger if it is reached, it is possible to proceed this error using the
proceed function PROCEED.
If DATUM is a condition, then that condition is used directly.
In this case, ARGUMENTS will be used only with the PROCEED-FORMAT-STRING
and will not be used to initialized DATUM.
If DATUM a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-ERROR
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
The PROCEED-FORMAT-STRING must be a string. Note that if DATUM is not a
string, then the format arguments used by the PROCEED-FORMAT-STRING will
still be the ARGUMENTS (in the keyword format as specified). In this case,
some care may be necessary to set up the PROCEED-FORMAT-STRING correctly.
The format op }* may be particularly useful in this situation.
The value returned by CERROR is the condition which was signaled.
Error Proposal #8 by KMP 8/4/86, Page 14
BREAK &optional datum &rest arguments [Function]
Directly enters the debugger with a condition without trying to invoke
the signal facility. Executing the function PROCEED while in the debugger
will cause a return from the BREAK.
If DATUM is a condition, then that condition is used directly.
In this case, it is an error for ARGUMENTS to be non-NIL.
If DATUM is a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-CONDITION
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
If DATUM is not supplied, it defaults to "Break".
If the break is proceeded, the value returned is the condition that was used.
Implementation Note: BREAK could be defined by:
(DEFUN BREAK (DATUM &REST ARGUMENTS)
(PROCEED-CASE (DEBUG
(COND ((TYPEP DATUM 'CONDITION) DATUM)
((SYMBOLP DATUM) ;roughly, (SUBTYPEP DATUM 'CONDITION)
(APPLY #'MAKE-CONDITION DATUM ARGUMENTS))
((STRINGP DATUM)
(MAKE-CONDITION 'SIMPLE-CONDITION
:FORMAT-STRING DATUM
:FORMAT-ARGUMENTS ARGUMENTS))
(T
(ERROR "Bad argument to BREAK: }S" DATUM))))
(PROCEED (CONDITION)
:REPORT "Return from BREAK."
CONDITION)))
Error Proposal #8 by KMP 8/4/86, Page 15
WARN datum &rest arguments [Function]
Invokes the signal facility on a condition. If the condition is not handled,
then the text of the warning is output to error output (with possible
implementation-specific extras such as moving to a fresh line before and
after the display of the warning, or supplying some introductory that might
mention the name of the function which called WARN). If *BREAK-ON-WARNINGS*
is true, then in addition to printing the warning, the debugger is entered.
In this case, WARN returns only if PROCEED is done from the debugger. The
value returned by WARN is the condition which was signaled.
If DATUM is a condition, then that condition is used directly.
In this case, it is an error for ARGUMENTS to be non-NIL.
If DATUM is a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-WARNING
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
*BREAK-ON-WARNINGS* [Variable]
{As described in CLtL⎇
Error Proposal #8 by KMP 8/4/86, Page 16
DEFINE-CONDITION name parent-type [keyword value]* &rest slots
[Special Form]
Defines a new condition type with the given NAME, which is a
subtype of the given PARENT-TYPE. Except as otherwise noted,
the arguments are not evaluated.
The valid KEYWORD/VALUE pairs are:
:CONC-NAME symbol-or-string
As in DEFSTRUCT, this sets up automatic prefixing of the names
of slot accessors. Also as in DESTRUCT, the default behavior is to use
the name of the new type, NAME, followed by a hyphen.
:REPORT-FUNCTION expression
EXPRESSION should be a suitable argument to the FUNCTION special form.
It designates a function of two arguments, a condition and a stream,
which prints the condition to the stream when *PRINT-ESCAPE* is NIL.
:REPORT form
A short form of :REPORT-FUNCTION to cover two common cases.
If form is a constant string, this is the same as
:REPORT-FUNCTION (LAMBDA (CONDITION STREAM)
(DECLARE (IGNORE CONDITION))
(WRITE-STRING form STREAM))
Otherwise, this is similar to:
:REPORT-FUNCTION (LAMBDA (CONDITION *STANDARD-OUTPUT*) form)
EXCEPT that the evaluation of form takes place in an environment where
all slots of the condition are accessible through variables of the same
name. For example, in the report function for an arithmetic error, the
variables OPERATIONS and OPERANDS are available. In some implementations,
this may happen naturally via instance variables and in other
implementations there may be an intervening LET to create the necessary
bindings. It is an error to assign any variables which have been set up
in this way. In this latter case, the form describes how to print objects
of the type being defined. The form should do output to standard output.
The condition being printed will be the value of the variable CONDITION.
:HANDLER-FUNCTION expression
EXPRESSION should be a suitable argument to the FUNCTION special form.
It designates a function of one argument, a condition, to be the default
handler for this condition type.
:HANDLE form
An expression to be used as the body of a default handler for this
condition type. While executing form, the variable CONDITION will
be bound to the condition being handled.
It is an error to specify both :REPORT-FUNCTION and :REPORT in the
same DEFINE-CONDITION. If neither :REPORT-FUNCTION nor :REPORT is specified,
information about how to print this type of condition will be inherited
from the PARENT-TYPE.
SLOTS is a list of slot spec, and specifies slots to be used by the
given type. In addition to those specified, the slots of the PARENT-TYPE
are also available.
A slot spec is either the name of a slot, or a list, the car of
which is the slot name and the cadr is a form which will can evaluated
by MAKE-CONDITION to produce a default value when an explicit value
is not provided. If no slot default is specified, NIL is assumed.
If a slot name is specified which is the same as the name in some
type of which this type is a subtype, only one shared slot is created
in the condition object, but the specified default overrides any
default which might otherwise have been inherited from a parent type.
Error Proposal #8 by KMP 8/4/86, Page 17
MAKE-CONDITION will accept keywords with the printname of any of
the designated slots, and will initialize the corresponding slots in
conditions it creates.
If the :REPORT or :HANDLE keyword is used in a DEFINE-CONDITION, all
slots of the structure will be automatically accessible to the given
expression by way of variables of the same name. It is an error to
assign any such variable, however; they are intended only for read-only
purposes.
Accessors are created according to the same rules as used by DEFSTRUCT.
For example:
(DEFINE-CONDITION PEG/HOLE-MISMATCH BLOCKS-WORLD-ERROR
:REPORT (FORMAT T "A }A peg cannot go in a }A hole."
PEG-SHAPE HOLE-SHAPE)
PEG-SHAPE
HOLE-SHAPE)
defines a condition of type PEG/HOLE-MISMATCH which inherits from the
BLOCKS-WORLD-ERROR condition type. The new type has slots PEG-SHAPE and
HOLE-SHAPE so that MAKE-CONDITION will accept :PEG-SHAPE and :HOLE-SHAPE
keywords, and accessors PEG/HOLE-MISMATCH-PEG-SHAPE and
PEG/HOLE-MISMATCH-HOLE-SHAPE will apply to objects of this type.
No functions are provided for directly accessing the printer or the default
handler for a condition since it is not believed that this will be very
useful in portable code (at least until an object system is better defined).
The printer for a condition will be implicitly called any time a condition
is printed with *PRINT-ESCAPE* being NIL. Hence, (PRINC condition) or
(FORMAT T "}A" condition) are possible ways of invoking the condition's
printer.
In a :REPORT clause, output should be done to standard output, not to
error output. This is because conditions may be asked to report at other
times than when an error is being announced. When an error is being
announced, standard output will be redirected to error output.
The default handler will be implicitly called during the signaling process
and never has any reason to be manually invoked.
Here are some examples of creating conditions. These forms define a condition
called MACHINE-ERROR which inherits from ERROR:
(DEFINE-CONDITION MACHINE-ERROR ERROR
:REPORT (FORMAT T "There is a problem with }A." MACHINE-NAME)
MACHINE-NAME)
This defines a new error condition (a subtype of MACHINE-ERROR) for use when
machines are not available:
(DEFINE-CONDITION MACHINE-NOT-AVAILABLE-ERROR MACHINE-ERROR
:REPORT (FORMAT T "The machine }A is not available." MACHINE-NAME))
This defines a still more specific condition, built upon
MACHINE-NOT-AVAILABLE-ERROR, which provides a default for MACHINE-NAME but
which does not provide any new slots:
(DEFINE-CONDITION MY-FAVORITE-MACHINE-NOT-AVAILABLE-ERROR
MACHINE-NOT-AVAILABLE-ERROR
(MACHINE-NAME "MIT-MC.ARPA"))
This gives the MACHINE-NAME slot a default initialization. Since no :REPORT
clause was given, the information supplied in the definition of
MACHINE-NOT-AVAILABLE-ERROR will be used if a condition of this type is
printed while *PRINT-ESCAPE* is NIL.
Error Proposal #8 by KMP 8/4/86, Page 18
MAKE-CONDITION type &rest slot-initializations [Function]
Calls the appropriate constructor function for the given type, passing
along the given slot initializations to the constructor, and returning
an instantiated condition.
The SLOT-INITIALIZATIONS are given in alternating keyword/value pairs.
eg, (MAKE-CONDITION 'PEG/HOLE-MISMATCH :PEG-SHAPE 'SQUARE :HOLE-SHAPE 'ROUND)
Design Note: If either DEFSTRUCT or some other type system adopted
later by CL ever provides a MAKE-INSTANCE primitive, MAKE-CONDITION
would no longer be necessary.
SIGNAL datum &rest arguments [Function]
Invokes the signal facility on a condition. If the condition is not handled,
SIGNAL returns the condition object it was attempting to handle.
If DATUM is a condition, then that condition is used directly.
In this case, it is an error for ARGUMENTS to be non-NIL.
If DATUM is a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-CONDITION
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
CONDITION-BIND bindings &rest forms [Special Form]
Executes body in a dynamic context where the giving local handler bindings
are in effect. The BINDINGS must take the form (type handler).
TYPE may be a type or a list of types.
HANDLER should evaluate to a function to be used to handle conditions of the
given type(s) during execution of the FORMS.
Error Proposal #8 by KMP 8/4/86, Page 19
DEBUG &optional datum &rest arguments [Function]
Enters the debugger with a given condition.
If DATUM is a condition, then that condition is used directly.
In this case, it is an error for ARGUMENTS to be non-NIL.
If DATUM is a condition type, then the condition used is the result
of doing (APPLY #'MAKE-CONDITION datum arguments).
If DATUM is a string, then the condition used is the result of
doing (MAKE-CONDITION 'SIMPLE-CONDITION
:FORMAT-STRING datum
:FORMAT-ARGUMENTS arguments).
If DATUM is not supplied, it defaults to "Break".
This function will never directly return. Return can occur only by a
special transfer of control, such as to a PROCEED-CASE or CATCH-ABORT.
The exact way in which the debugger interacts with users is expected to vary
considerably from system to system. For example, some systems may use a
keyboard interface while others may use a mouse interface. Of those systems
using keyboard commands, some may use single-character commands while others
use parsed line-at-a-time commands. The exact set of commands will vary as
well. The important properties of a debugger are that:
* It makes information about the error accessible.
* It makes the set of apparent proceed cases easily accessible.
Note: It is desirable to have a mode where the debugger allows other
features, such as the ability to inspect data, stacks, etc. However,
it may sometimes be appropriate to have this kind of information hidden
from users. Experience on the Lisp Machines has shown that some users
who are not programmers develop a terrible phobia of debuggers. The reason
for this usually traces to the fact that the debugger is very foreign to
them and provides an overwhelming amount of information which is only of
interest to programmers. With the advent of proceed cases, there is a clear
mechanism for the construction of "friendly" debuggers. Programmers can
be taught how to get to the information they need for debugging, but it
should be possible to construct user interfaces to the debugger which
are natural, convenient, intelligible, and friendly even to non-programmers.
Error Proposal #8 by KMP 8/4/86, Page 20
COMPUTE-PROCEED-CASES [Function]
Uses the dynamic state of the program to compute a list of ``proceed
cases'' which are dynamically active.
Each ``proceed case'' represents a point in the current dynamic state of
the program to which control may be transferred. Implementations are free
to implement these objects in whatever manner is most convenient; the
objects need have only dynamic extent. The only operations which
Common Lisp defines for such objects are PROCEED-CASE-NAME,
FIND-PROCEED-CASE, INVOKE-PROCEED-CASE, PRINC and PRINT, the
identification of an object as a proceed case using
(TYPEP x 'PROCEED-CASE), and standard lisp operations which work for
all objects such as EQ, EQL, DESCRIBE, etc.
The list which results from a call to COMPUTE-PROCEED-CASES is ordered
so that the innermost (ie, more-recently established) proceed cases
are nearer the head of the list.
Note, too, that COMPUTE-PROCEED-CASES returns all valid proceed cases for
CONDITION, even if some of them have the same name as others and therefore
would not be found by FIND-PROCEED-CASE.
Implementations are permitted, but not required, to return different
(ie, non-EQ) lists from repeated calls to COMPUTE-PROCEED-CASES while
in the same lexical/dynamic environment. It is an error to modify the
list which is returned by COMPUTE-PROCEED-CASES.
Error Proposal #8 by KMP 8/4/86, Page 21
When a condition is signaled, a facility is available for use by handlers to
non-locally transfer control to an outer dynamic contour of the program. The
form which creates contours which may be returned to is called PROCEED-CASE.
The function which transfers control to a PROCEED-CASE clause is called
INVOKE-PROCEED-CASE.
PROCEED-CASE form &rest clauses [Special Form]
The form is evaluated in a dynamic context where the clauses have
special meanings as points to which control may be transferred in the
event that a condition is signaled. If form runs to completion and
returns any values, all values returned by the form are simply returned
by the PROCEED-CASE form. If a condition is signaled while form is
running, a handler may tranfer control to one of the clauses. If a
transfer occurs, the forms in the body of that clause will be evaluated
and any values returned by the last such form will be returned by the
PROCEED-CASE form.
A PROCEED-CASE clause has the form:
(case-name arglist [keyword value]* [body-form]*)
The CASE-NAME may be NIL or a symbol naming this proceed case.
It may sometimes be desirable to have more than one clause use the same
CASE-NAME. This is perfectly fine and very well-defined. The first clause
with that name will be found by FIND-PROCEED-CASE and indirectly
by the named proceed function, but the other clause is accessible through
COMPUTE-PROCEED-CASES.
The ARGLIST is a list of variables to be bound during the execution of
the body-forms. These arguments are used to pass any necessary data from
the condition handler to the PROCEED-CASE clause. If a non-NIL CASE-NAME
was supplied and corresponds to a defined proceed function, then the
arguments in the arglist need not be optional, since the proceed-function
or the call to INVOKE-PROCEED-CASE will take care of resolving optional
arguments.
The valid KEYWORD/VALUE pairs are:
:REPORT-FUNCTION exp
The EXP must be an appropriate argument to the FUNCTION special form,
and should designate a function of two arguments, a proceed case and
a stream, which summarizes the action that this proceed case will take.
:REPORT form
This is a shorthand for two important special cases of :REPORT-FUNCTION.
If FORM is a constant string, then this is the same as:
:REPORT-FUNCTION (LAMBDA (STREAM) (WRITE-STRING form STREAM))
Otherwise, this is the same as
:REPORT-FUNCTION (LAMBDA (*STANDARD-OUTPUT*) form)
In the latter case, form must do output to standard output, summarizing
the action that this proceed case will take.
Error Proposal #8 by KMP 8/4/86, Page 22
Only one of :REPORT or :REPORT-FUNCTION may be specified.
If :REPORT or :REPORT-FUNCTION is specified in the proceed case, then only
that information is considered, and any :REPORT or :REPORT-FUNCTION
specified as a default for the named proceed function is not used.
If a named proceed function is used but no report information is supplied,
the name of the proceed function is used to generate the default help
information. It is an error if no named proceed case is used and no report
information is provided; implementations are encouraged to flag this error
at the earliest convenient time (eg, compilation time).
When *PRINT-ESCAPE* is NIL, the printer will use report information for
a proceed case. eg, a debugger might announce the action of typing
Control-Z by doing:
(FORMAT T "}&}C: }A}%" #\Control-Z SOME-PROCEED-CASE)
which might then display as something like:
Control-Z: Return to command level.
Error Proposal #8 by KMP 8/4/86, Page 23
Examples:
(PROCEED-CASE (APPLY FUNCTION SOME-ARGS)
(NEW-FUNCTION (NEW-FUNCTION)
(SETQ FUNCTION NEW-FUNCTION)))
(PROCEED-CASE (APPLY FUNCTION SOME-ARGS)
(NIL (&OPTIONAL (NEW-FUNCTION
(READ-TYPED-OBJECT 'FUNCTION "Function: ")))
:REPORT "Use a different function."
(SETQ FUNCTION NEW-FUNCTION)))
(PROCEED-CASE (A-COMMAND-LOOP)
(RETURN-FROM-COMMAND-LEVEL ()
:REPORT (FORMAT T "Return from command level }D." LEVEL)
NIL))
(LOOP
(PROCEED-CASE (ANOTHER-RANDOM-COMPUTATION)
(PROCEED ())))
Assuming that NEW-FUNCTION is defined as a proceed function with
:REPORT "Use a different function."
then the first and second example are equivalent from the point of view of
someone using the interactive debugger, but differ in one important aspect
for non-interactive handling. If a handler `knows about' proceed function
names, as in: (IF (FIND-PROCEED-CASE 'NEW-FUNCTION)
(NEW-FUNCTION THE-REPLACEMENT))
then only the first example, and not the second, will have control transfered
to its correction clause.
Here's a more complete example:
(LET ((MY-FOOD 'MILK)
(MY-COLOR 'GREENISH-BLUE))
(DO ()
((NOT (BAD-FOOD-COLOR-P MY-FOOD MY-COLOR)))
(PROCEED-CASE (ERROR 'BAD-FOOD-COLOR :FOOD MY-FOOD :COLOR MY-COLOR)
(USE-FOOD (NEW-FOOD) (SETQ MY-FOOD NEW-FOOD))
(USE-COLOR (NEW-COLOR) (SETQ MY-COLOR NEW-COLOR))))
;; We won't get to here until MY-FOOD and MY-COLOR are compatible.
(LIST MY-FOOD MY-COLOR))
A handler can then proceed the error in either of two ways. It may correct
the color or correct the food. For example:
#'(LAMBDA (CONDITION) ... (USE-COLOR 'WHITE) ...) ;Corrects color
or #'(LAMBDA (CONDITION) ... (USE-FOOD 'CHEESE) ...) ;Corrects food
Here is an example using CONDITION-BIND and SIGNAL-CASE...
(CONDITION-BIND ((FOO-ERROR #'(LAMBDA (IGNORE) (USE-VALUE 7))))
(PROCEED-CASE (ERROR 'FOO-ERROR)
(USE-VALUE (X) (* X X))))
=> 49
Error Proposal #8 by KMP 8/4/86, Page 24
PROCEED-CASE-NAME proceed-case [Function]
Returns the name of the given PROCEED-CASE, or NIL if it is not named.
FIND-PROCEED-CASE name [Function]
Searches for an active proceed case by the given NAME in the current
dynamic contour.
If NAME is a proceed function name, then the innermost (ie, most recently
established) proceed case with that function name that matches the given
condition is returned. NIL is returned if no such proceed case is found.
If NAME is a proceed case object, then it is simply returned unless it
is not currently valid for use. In that case, NIL is returned.
It is an error for NIL to be given as a name. Applications which would
seem to require this should be rewritten to make appropriate use of
COMPUTE-PROCEED-CASES instead.
INVOKE-PROCEED-CASE proceed-case &rest values
[Function]
Transfers control to the given PROCEED-CASE, passing the given VALUES.
The PROCEED-CASE must a proceed case or the name of a proceed function
which is valid in the current dynamic context. If the argument is not
valid, an error will be signaled.
If the proceed case is a named proceed case, this primitive should do
optional argument resolution similar to that done by named proceed
function. (In the case where it is being called by a named proceed
function, that resolution will be redundant, but in the case of direct
invocation of INVOKE-PROCEED-CASE, it will not be.)
This operation is used primarily as a sub-primitive for implementing
named proceed functions, but may be necessary when writing certain kinds
of portable, interactive debuggers. Named proceed functions call
this function, not vice versa.
See Footnote: {INVOKE-PROCEED-CASE vs Named Proceed Functions⎇
Error Proposal #8 by KMP 8/4/86, Page 25
DEFINE-PROCEED-FUNCTION name [keyword value]* &rest variables
[Special Form]
Valid KEYWORD/VALUE pairs are the same as those which are defined for
the PROCEED-CASE special form. That is, :REPORT-FUNCTION and :REPORT.
This form defines a function called NAME which will proceed an error in
a typed way. The proceed function takes a required argument of a condition
and optional arguments which are given by the VARIABLES specification.
Each element of VARIABLES has the form
VARIABLE-NAME
or (VARIABLE-NAME)
or (VARIABLE-NAME INITIAL-VALUE).
or (VARIABLE-NAME INITIAL-VALUE SUPPLIED-P-VARIABLE)
If initial-value is not supplied, it defaults to NIL.
If a SUPPLIED-P-VARIABLE is given, it can be used only for the computation
of default values for arguments which follow. It will not be passed through
to the proceed case.
For example, here are some possible proceed functions which might be useful
in conjunction with the BAD-FOOD-COLOR error we used as an example earlier:
(DEFINE-PROCEED-FUNCTION USE-FOOD
:REPORT "Use another food."
(FOOD (READ-TYPED-OBJECT 'FOOD "Food to use instead: ")))
(DEFINE-PROCEED-FUNCTION USE-COLOR
:REPORT "Change the food's color."
(COLOR (READ-TYPED-OBJECT 'FOOD "Color to make the food: ")))
(DEFUN MAYBE-USE-WATER (CONDITION) ;A sample named handler
(IF (EQ (BAD-FOOD-COLOR-FOOD CONDITION) 'MILK)
(USE-FOOD 'WATER)))
(CONDITION-BIND ((BAD-FOOD-COLOR #'MAYBE-USE-WATER))
...)
The condition argument to a proceed function is optional. If not provided,
it defaults to NIL. This may be useful in the implementation of proceed
functions such as ABORT, which are not normally called with a condition argument.
If a named proceed function is invoked in a context in which there is
no active proceed case by that name, the proceed function simply returns NIL.
(Processing of optional arguments in this case will still have to occur, so
in the case of invoking such a function non-interactively, it is recommended
that all optional arguments be explicitly supplied to avoid embarrassing
prompting for values which will not be used.) For example, in each of the
following pairs of handlers, the first is equivalent to the second except for
efficiency:
#'(LAMBDA (CONDITION) ;OK, but slow
(DECLARE (IGNORE CONDITION))
(IF (FIND-PROCEED-CASE 'USE-FOOD) (USE-FOOD 'MILK)))
#'(LAMBDA (CONDITION) ;Preferred
(DECLARE (IGNORE CONDITION))
(USE-FOOD 'MILK))
-----
#'(LAMBDA (CONDITION) ;OK, but slow
(DECLARE (IGNORE CONDITION))
(COND ((FIND-PROCEED-CASE 'USE-FOOD)
(USE-FOOD 'CHOCOLATE))
((FIND-PROCEED-CASE 'USE-COLOR)
(USE-COLOR 'ORANGE))))
#'(LAMBDA (CONDITION) ;Preferred
(DECLARE (IGNORE CONDITION))
(USE-FOOD 'CHOCOLATE)
(USE-COLOR 'ORANGE))
Error Proposal #8 by KMP 8/4/86, Page 26
*** NOTE: In contrast to the way that Zetalisp has defined ABORT as a kind
*** of condition to be handled, we define ABORT as a manner of proceeding
*** a condition rather than as a condition type to be handled.
CATCH-ABORT print-form &body forms [Macro]
Sets up a PROCEED-CASE context for the proceed function ABORT.
If no ABORT is done while executing FORMS, all values returned by the last
form in FORMS are returned. If an ABORT transfers control to this CATCH-ABORT,
two values are returned: NIL and the condition which was given to ABORT
(or NIL if none was given).
There must be at least one form in FORMS.
CATCH-ABORT could be defined by:
(DEFMACRO CATCH-ABORT (PRINT-FORM FORM1 &REST MORE-FORMS)
`(PROCEED-CASE (PROGN FORM1 ,@MORE-FORMS)
(ABORT (CONDITION)
:REPORT ,PRINT-FORM
(VALUES NIL CONDITION))))
Example:
Lisp> (DEFUN READ-EVAL-PRINT-LOOP (LEVEL)
(CATCH-ABORT (FORMAT T "Exit command level }D." LEVEL)
(LOOP
(CATCH-ABORT (FORMAT T "Return to command level }D." LEVEL)
(PRINT (EVAL (READ)))))))
=> READ-EVAL-PRINT-LOOP
Lisp> (READ-EVAL-PRINT-LOOP 1)
(+ 'a 3)
Error: The argument, A, to the function + was of the wrong type.
The function expected a number.
To proceed, type CONTINUE followed by an option number:
0: Specify a value to use this time.
1: Return to command level 1
2: Exit command level 1
3: Return to Lisp Toplevel
Debug>
Error Proposal #8 by KMP 8/4/86, Page 27
PROCEED &optional condition [Function]
A proceed function with default
:REPORT "Proceed with no special action."
If not specified, CONDITION defaults to NIL.
ABORT &optional condition [Function]
A proceed function with default
:REPORT "Abort."
If not specified, CONDITION defaults to NIL.
USE-VALUE &optional value [Function]
A proceed function with default
:REPORT "Specify a value to use this time."
If not specified, VALUE is interactively prompted for.
STORE-VALUE &optional value [Function]
A proceed function with default
:REPORT "Specify a value to store permanently and use this time."
If not specified, VALUE is interactively prompted for.
Error Proposal #8 by KMP 8/4/86, Page 28
CONDITION-CASE form &rest cases [Special Form]
Executes the given form. Each case has the form (type ([var]) . body).
If a condition is signaled (and not handled by an intervening handler)
during the execution of the form for which there is an appropriate clause
-- ie, one for which (TYPEP condition 'type) is true -- then control is
transferred to the body of the relevant clause, binding the given VAR to
the condition which was signaled. If no error occurs, then the values
resulting from the FORM are returned by the CONDITION-CASE.
If more than one case is provided, an implicit nesting occurs. That is,
(CONDITION-CASE form
(type1 (var1) form1)
(type2 (var2) form2))
is equivalent to
(CONDITION-CASE (CONDITION-CASE form
(type1 (var1) form1))
(type2 (var2) form2))
If VAR is not needed, it may be omitted. That is, a clause such as:
(type (var) (DECLARE (IGNORE var)) form)
may be written simply:
(type () form)
TYPE may also be a list of types, in which case it will catch conditions of
any of the specified types.
Examples:
(CONDITION-CASE (/ X Y)
(DIVISION-BY-ZERO () NIL))
(CONDITION-CASE (OPEN *THE-FILE* :DIRECTION :INPUT)
(FILE-ERROR (CONDITION) (FORMAT T "}&Open failed: }A}%" CONDITION)))
(CONDITION-CASE (SOME-USER-FUNCTION)
(FILE-ERROR (CONDITION) CONDITION)
(DIVISION-BY-ZERO () 0)
((UNBOUND-VARIABLE UNDEFINED-FUNCTION) () 'UNBOUND))
IGNORE-ERRORS &body forms [Macro]
Executes its body in a context which handles errors of type ERROR
by returning control to this form. If no error is signaled, any
return values returned by the last form are returned by IGNORE-ERRORS.
Otherwise, NIL is returned.
Synonym for (CONDITION-CASE (PROGN . forms)
(ERROR () NIL)).
Although they are not described in detail here, it is expected that the
macros CHECK-TYPE, ASSERT, ETYPECASE, CTYPECASE, ECASE, and CCASE should
continue to be supported essentially as described in CLtL.
Error Proposal #8 by KMP 8/4/86, Page 29
PRE-DEFINED TYPES
-----------------
Implementation Note: The types PROCEED-CASE, CONDITION, etc. are new ``first
class'' types in the sense that they must be distinguishable from any other
CL types of which they are subtypes. This is necessary because PRINC is
specified to treat them specially. DEFSTRUCT is one way to implement these
types.
PROCEED-CASE [Type]
This is the data type used to represent a proceed case.
The CONDITION type hierarchy looks like this:
CONDITION
!
+---------------+---------------+
! ! !
SIMPLE-CONDITION WARNING SERIOUS-CONDITION
! !
SIMPLE-WARNING !
!
!
+------------+---------+
! !
STORAGE-CONDITION ERROR
! !
+--------+--------+ !
! ! !
STACK-OVERFLOW STORAGE-EXHAUSTED !
!
!
+-----------+---+------------------+--- . . .
! ! !
SIMPLE-ERROR ARITHMETIC-ERROR CONTROL-ERROR
The types which are non-terminals in the above tree (ie, CONDITION,
WARNING, SERIOUS-CONDITION, STORAGE-CONDITION, ERROR, ARITHMETIC-ERROR,
CONTROL-ERROR, etc.) are provided primarily for type inclusion
purposes. Normally, they would not be directly instantiated.
Implementations are permitted to support non-portable synonyms for
these types, as well as to introduce other types which above, below,
or between the types shown in this tree as long as the indicated
subtype relationships are not violated.
See Footnote: {TYPEP vs CONDITION-TYPEP⎇
Error Proposal #8 by KMP 8/4/86, Page 30
CONDITION [Type]
All types of conditions, whether error or non-error, must inherit from
this type.
WARNING [Type]
All types of warnings should inherit from this type.
This is a subtype of CONDITION.
SERIOUS-CONDITION [Type]
Any condition, whether error or non-error, which should enter the debugger
when signaled but not handled should inherit from this type. This is a
subtype of CONDITION.
Note: IGNORE-ERRORS will ignore conditions of type ERROR, not of type
SERIOUS-CONDITION. Conditions which are serious conditions but not errors
are typically those that may require more sophisticated handling than
simply being ignored. For example, IGNORE-ERRORS will not ignore a
STORAGE-CONDITION, which is a serious condition but is not generally
a program error.
Compatibility Note: SERIOUS-CONDITION is similar to Zetalisp's
DBG:DEBUGGER-CONDITION.
ERROR [Type]
All types of error conditions inherit from this condition.
This is a subtype of CONDITION.
Error Proposal #8 by KMP 8/4/86, Page 31
The default condition type for SIGNAL, BREAK, and DEBUG is SIMPLE-CONDITION,
and for ERROR and CERROR is SIMPLE-ERROR.
SIMPLE-CONDITION [Type]
Conditions signaled by SIGNAL when given a format string as a first
argument are of this type. This is a subtype of CONDITION.
The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported.
SIMPLE-WARNING [Type]
Conditions signaled by WARN when given a format string as a first
argument are of this type. This is a subtype of WARNING.
The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported.
SIMPLE-ERROR [Type]
Conditions signaled by ERROR and CERROR when given a format string
as a first argument are of this type. This is a subtype of ERROR.
The init keywords :FORMAT-STRING and :FORMAT-ARGUMENTS are supported.
Error Proposal #8 by KMP 8/4/86, Page 32
STORAGE-CONDITION [Type]
Conditions which relate to memory overflow conditions should inherit
from this type. This is a subtype of SERIOUS-CONDITION.
STACK-OVERFLOW [Type]
Conditions which relate to stack overflow should inherit from this type.
This is a subtype of STORAGE-CONDITION.
STORAGE-EXHAUSTED [Type]
Conditions which relate to any kind of GC overflow should inherit from
this type. This is a subtype of STORAGE-CONDITION.
-------------------------
CONTROL-ERROR [Type]
Errors in the transfer of control in a program should inherit from
this type. This is a subtype of ERROR.
ILLEGAL-THROW [Type]
The error which results when THROW is given a tag which is not active
should inherit from this. This is a subtype of CONTROL-ERROR. The
function ILLEGAL-THROW-TAG will access the offending tag.
ILLEGAL-GO [Type]
The error which results when GO is given a tag which is no longer
available should inherit from this. This is a subtype of CONTROL-ERROR.
The function ILLEGAL-GO-TAG will access the offending tag.
-------------------------
STREAM-ERROR [Type]
Errors which occur during input from or output to a stream should
inherit from this type. This is a subtype of ERROR. The function
STREAM-ERROR-STREAM will access the offending stream.
READ-ERROR [Type]
Errors which occur during an input operation on a stream should inherit
from this type. This is a subtype of STREAM-ERROR.
END-OF-FILE [Type]
The error which results when a read operation is done on a stream which has no
more tokens should inherit from this type. This is a subtype of READ-ERROR.
Error Proposal #8 by KMP 8/4/86, Page 33
FILE-ERROR [Type]
Errors which occur during an attempt to open or close a file, or during
some low-level transaction with a file system should inherit from this type.
This is a subtype of ERROR. The function FILE-ERROR-PATHNAME will access the
pathname for of the offending file.
-------------------------
CELL-ERROR [Type]
Errors which occur while accessing a location should inherit from this
type. This is a subtype of ERROR. The function CELL-ERROR-NAME will
access the name of the offending cell.
UNBOUND-VARIABLE [Type]
The error which results from trying to access the value of an unbound
variable should inherit from this type. This is a subtype of CELL-ERROR.
UNDEFINED-FUNCTION [Type]
The error which results from trying to access the value of an undefined
function should inherit from this type. This is a subtype of CELL-ERROR.
-------------------------
ARITHMETIC-ERROR [Type]
Errors which occur while doing arithmetic type operations should inherit
from this type. This is a subtype of ERROR. The functions
ARITHMETIC-ERROR-OPERATION and ARITHMETIC-ERROR-OPERANDS will access the
offending operation and arguments, respectively.
DIVISION-BY-ZERO [Type]
Errors which occur because of division by 0 should inherit from this type.
This is a subtype of ARITHMETIC-ERROR.
FLOATING-POINT-OVERFLOW [Type]
Errors which occur because of floating point overflow should inherit from
this type. This is a subtype of ARITHMETIC-ERROR.
FLOATING-POINT-UNDERFLOW [Type]
Errors which occur because of floating point underflow should inherit from
this type. This is a subtype of ARITHMETIC-ERROR.
Error Proposal #8 by KMP 8/4/86, Page 34
FOOTNOTES
---------
{INVOKE-PROCEED-CASE vs Named Proceed Functions⎇
Some readers may wonder why there both proceed functions and an
INVOKE-PROCEED-CASE primitive.
The reason for named proceed functions is to formalize interfaces,
while the reason for PROCEED-CASE-INVOKE is to provide flexible
and portable access to the mechanism.
INVOKE-PROCEED-CASE has the following two purposes:
* As a sub-primitive, to implement named proceed functions.
* To invoke shadowed proceed cases (usually in an interactive debugger).
* To invoke anonymous proceed cases. (almost always in an interactive debugger).
Writers of portable code are encouraged to prefer the use of named proceed
functions over the use of INVOKE-PROCEED-CASE in any situation where it is
feasible. Nevertheless, it should be recognized that some useful, portable
programs (most importantly, interactive debuggers) can only be written using
INVOKE-PROCEED-CASE.
{TYPEP vs CONDITION-TYPEP⎇
An earlier version of this proposal used a function called CONDITION-TYPEP
rather than using real TYPEP. Over MLY's objections, I finally chose to
use TYPEP for the following reasons:
* We need a type system which can deal with these issues. Reducing the
need for TYPEP to address these issues might reduce the perceived need
for us to converge on a better type system.
* If we later adopt a better type system, it is possible that any
CONDITION-TYPEP introduced now would have different inheritance rules.
It would be undue burden on the user to have to remember the rules
for more than one kind of type system.
* Introducing a CONDITION-TYPEP sets up a precedent for the introduction
of other xxx-TYPEP operations. It would be too easy for such type systems
to vary in subtle ways. It is better to simply insist that TYPEP be
good enough as is, or be improved until it is good enough.
Error Proposal #8 by KMP 8/4/86, Page 35
{:REPORT vs :REPORT-FUNCTION⎇
Originally, I had :REPORT-FUNCTION, :REPORT-STRING, and :REPORT.
Moon objected to a bunch of keywords which did similar things. He wanted
just one keyword. He also objected to the new term "report", feeling that
"print" was adequate.
I circulated a draft proposal with just a :PRINT option that did what the
:REPORT option does in this proposal, but with no analog for the
:REPORT-FUNCTION. I wasn't completely happy with the absence of
:REPORT-FUNCTION but figured I'd live with it if it saved a lot of fuss.
MLY objected to the absence of :PRINT-FUNCTION. So I backed up a bit
and decided that there were probably really two camps and that we should
just serve both. MLY also wondered why this option controlled typeout
only in the case where *PRINT-ESCAPE* was NIL. So I backed up and introduced
the term "report" instead of PRINT so that people wouldn't wonder why
the :PRINT option didn't control the entire behavior of PRIN1 and PRINT.
The final result of this was to revert back to :REPORT and :REPORT-FUNCTION,
but to omit :REPORT-STRING as being unnecessary. I hope that no one will
argue a lot about this -- it will serve most people's needs and I think is
the sort of thing we could go around and around about to no good end.
Error Proposal #8 by KMP 8/4/86, Page 36
REFERENCES
----------
Some background information about motivation for decisions in this proposal
may be found in my paper ``Exceptional Situations in Lisp,'' which is
available as A.I. Working Paper 268 from the MIT AI Lab publications office
(545 Technology Square, Cambridge, MA 02139).
Much of the basis of ``Exceptional Situations in Lisp'' derives from
``Signalling and Handling Conditions,'' a document published by Symbolics,
Inc. which describes the Lisp Machine's condition system as it looked when
originally introduced in 1983.
Error Proposal #8 by KMP 8/4/86, Page 37
INDEX
-----
ABORT ............................................. Function ....... 27
ARITHMETIC-ERROR .................................. Type ........... 33
ARITHMETIC-ERROR-OPERANDS ......................... Function ....... 33
ARITHMETIC-ERROR-OPERATION ........................ Function ....... 33
ASSERT ............................................ Macro .......... 28
Bound Handlers .................................... Concept ........ 12
BREAK ............................................. Function ....... 14
*BREAK-ON-WARNINGS* ............................... Variable ....... 15
CATCH-ABORT ....................................... Macro .......... 26
CCASE ............................................. Macro .......... 28
CELL-ERROR ........................................ Type ........... 33
CELL-ERROR-NAME ................................... Function ....... 33
CERROR ............................................ Function ....... 13
CHECK-TYPE ........................................ Macro .......... 28
COMPUTE-PROCEED-CASES ............................. Function ....... 20
:CONC-NAME ........................................ Keyword ........ 16
Condition ......................................... Concept ........ 4,7
CONDITION ......................................... Type ........... 30
Condition Type Hierarchy .......................... Diagram ........ 29
Condition Types ................................... Concept ........ 16
Condition Types, Creation of ...................... Concept ........ 16
Condition, Creation of ............................ Concept ........ 18
CONDITION-BIND .................................... Special Form ... 18
CONDITION-CASE .................................... Special Form ... 28
Conditions, Reporting of .......................... Concept ........ 12
CONTROL-ERROR ..................................... Type ........... 32
Creation of Condition Types ....................... Concept ........ 16
Creation of Conditions ............................ Concept ........ 18
CTYPECASE ......................................... Macro .......... 28
DEBUG ............................................. Function ....... 19
Declining ......................................... Concept ........ 7,12
Default Handlers .................................. Concept ........ 12
DEFINE-CONDITION .................................. Special Form ... 16
DEFINE-PROCEED-FUNCTION ........................... Function ....... 25
ECASE ............................................. Macro .......... 28
END-OF-FILE ....................................... Type ........... 32
Error ............................................. Concept ........ 4
ERROR ............................................. Function ....... 13
ERROR ............................................. Type ........... 30
ETYPECASE ......................................... Macro .......... 28
FIND-PROCEED-CASE ................................. Function ....... 24
:HANDLE ........................................... Keyword ........ 16
Handle ............................................ Concept ........ 7,12
Handlers, Bound ................................... Concept ........ 12
Handlers, Default ................................. Concept ........ 12
Handlers, Locating ................................ Concept ........ 12
Error Proposal #8 by KMP 8/4/86, Page 38
IGNORE-ERRORS ..................................... Macro .......... 28
ILLEGAL-GO ........................................ Type ........... 32
ILLEGAL-GO-TAG .................................... Function ....... 32
ILLEGAL-THROW ..................................... Type ........... 32
ILLEGAL-THROW-TAG ................................. Function ....... 32
INVOKE-PROCEED-CASE ............................... Function ....... 24
INVOKE-PROCEED-CASE vs Named Proceed Functions .... Footnote ....... 34
Locating Handlers ................................. Concept ........ 12
MAKE-CONDITION .................................... Function ....... 18
Proceed ........................................... Concept ........ 7
Proceed Cases, Reporting of ....................... Concept ........ 22
PROCEED-CASE ...................................... Special Form ... 21
PROCEED-CASE ...................................... Type ........... 29
PROCEED-CASE-NAME ................................. Function ....... 24
READ-ERROR ........................................ Type ........... 32
:REPORT ........................................... Keyword ........ 16,21
:REPORT vs :REPORT-FUNCTION ....................... Footnote ....... 35
:REPORT-FUNCTION .................................. Keyword ........ 16,21
Reporting of Conditions ........................... Concept ........ 12
Reporting of Proceed Cases ........................ Concept ........ 22
Serious Conditions ................................ Concept ........ 11
SERIOUS-CONDITION ................................. Type ........... 30
Signal ............................................ Concept ........ 5,7
SIGNAL ............................................ Function ....... 18
SIMPLE-CONDITION .................................. Type ........... 31
SIMPLE-ERROR ...................................... Type ........... 31
SIMPLE-WARNING .................................... Type ........... 31
STACK-OVERFLOW .................................... Type ........... 32
STORAGE-CONDITION ................................. Type ........... 32
STORAGE-EXHAUSTED ................................. Type ........... 32
STREAM-ERROR ...................................... Type ........... 32
STREAM-ERROR-STREAM ............................... Function ....... 32
TYPEP ............................................. Concept ........ 11
TYPEP vs CONDITION-TYPEP .......................... Footnote ....... 34
UNBOUND-VARIABLE .................................. Type ........... 33
UNDEFINED-FUNCTION ................................ Type ........... 33
WARN .............................................. Function ....... 15
WARNING ........................................... Type ........... 30